package com.mvc.framework.utils;

import com.mvc.framework.annotation.Controller;
import com.mvc.framework.annotation.RequestMapping;
import com.mvc.framework.entity.AnnotationBean;
import com.mvc.framework.entity.MethodInfo;
import com.mvc.framework.entity.ParamInfo;
import javassist.Modifier;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.objectweb.asm.*;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

public class AnnotationConfig {
	private AnnotationConfig() {
	}

	public static List<AnnotationBean> readAnnotationXml(String xmlPath) throws Exception {
		SAXBuilder builder = new SAXBuilder();
		File xml = new File(xmlPath);
		if (!xml.exists()) {
			throw new RuntimeException("annotation配置文件不存在，文件路径：" + xmlPath);
		}
		Document document = builder.build(new File(xmlPath));
		Element root = document.getRootElement();

		List<AnnotationBean> annotationBeanList = new ArrayList<>();
		Element scanPackage = root.getChild("scan-package");
		String scanpackagePath = scanPackage.getValue();
		Set<Class<?>> classes = findClassByPackage(scanpackagePath);

		if (classes != null && classes.size() > 0) {
			// 用于检查controller 的uri是不是重复了
			Set<String> uriSet = new HashSet<>();
			for (Class<?> claz : classes) {
				Controller controller = claz.getAnnotation(Controller.class);
				if (controller != null) {
					RequestMapping controllerRequestMapping = claz.getAnnotation(RequestMapping.class);

					AnnotationBean bean = new AnnotationBean();

					bean.setClazName(claz.getName());
					bean.setRootRequetPath(controllerRequestMapping != null ? controllerRequestMapping.value() : "");

					Method[] methods = claz.getMethods();
					Map<String, MethodInfo> methodInfoMap = new HashMap<>();
					// read method
					for (Method method : methods) {
						RequestMapping methodReqestMapping = (RequestMapping) method.getAnnotation(RequestMapping.class);
						if (methodReqestMapping == null) {
							continue;
						}
						MethodInfo methodInfo = new MethodInfo();
						methodInfo.setName(method.getName());
						// todo: 要不要检查 \ ？
						String uri = (methodReqestMapping.value() == null || "".equalsIgnoreCase(methodReqestMapping.value())) ? "/" :
								methodReqestMapping.value().startsWith("/") ? methodReqestMapping.value() : ("/" + methodReqestMapping.value());
						methodInfo.setUri(uri);

						//Set<ParamInfo> paramInfos = new LinkedHashSet<>();

						// 非静态的成员函数的第一个参数是this
						//Class<?>[] params =  method.getParameterTypes();
						// read params
						/*if (params != null && params.length > 0) {
							for (int i = 0; i < params.length; i++) {
								ParamInfo paramInfo = new ParamInfo();

								paramInfo.setName(params[i].getName());
								paramInfos.add(paramInfo);
							}
						}*/

						Set<ParamInfo> paramInfos = AnnotationConfig.getParameterNamesByAsm5(claz, method);
						methodInfo.setParamInfoList(paramInfos);
						// 检查controller内是否有重复URI
						if (methodInfoMap.get(methodInfo.getUri()) != null) {
							// 可自定义异常，此处使用一个意义相似但不准确的异常，本意是URI定义重复了
							throw new InputMismatchException(bean.getClazName() + " URI：" + methodInfo.getUri() + " 已定义，不能重复定义");
						}
						// 检查是否有重复URL
						if (uriSet.contains(bean.getRootRequetPath() + methodInfo.getUri())) {
							throw new InputMismatchException(bean.getClazName() + " URL：" + (bean.getRootRequetPath() + methodInfo.getUri()) + " 已定义，不能重复定义");
						} else {
							uriSet.add(bean.getRootRequetPath() + methodInfo.getUri());
						}
						methodInfoMap.put(methodInfo.getUri(), methodInfo);
					}


					bean.setMethodInfoMap(methodInfoMap);
					annotationBeanList.add(bean);
				}
			}
		}

		return annotationBeanList;
	}

	private static Set<ParamInfo> getParameterNamesByAsm5(Class<?> clazz, final Method method) {

		final Class<?>[] parameterTypes = method.getParameterTypes();
		if (parameterTypes == null || parameterTypes.length == 0) {
			return null;
		}
		final Type[] types = new Type[parameterTypes.length];
		for (int i = 0; i < parameterTypes.length; i++) {
			types[i] = Type.getType(parameterTypes[i]);
		}
		final Set<ParamInfo> parameterNames = new LinkedHashSet<>();
		String className = clazz.getName();
		int lastDotIndex = className.lastIndexOf(".");
		className = className.substring(lastDotIndex + 1) + ".class";
		InputStream is = clazz.getResourceAsStream(className);
		try {
			ClassReader classReader = new ClassReader(is);
			classReader.accept(new ClassVisitor(Opcodes.ASM5) {
				@Override
				public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
					// 只处理指定的方法
					Type[] argumentTypes = Type.getArgumentTypes(desc);
					if (!method.getName().equals(name) || !Arrays.equals(argumentTypes, types)) {
						return super.visitMethod(access, name, desc, signature, exceptions);
					}
					return new MethodVisitor(Opcodes.ASM5) {
						@Override
						public void visitLocalVariable(String name, String desc, String signature, org.objectweb.asm.Label start, org.objectweb.asm.Label end, int index) {
							ParamInfo paramInfo = null;
							// 非静态成员方法的第一个参数是this
							if (Modifier.isStatic(method.getModifiers()) || index > 0) {
								String clazType = null;
								if (index > 0) {
									clazType = types[index - 1].getClassName();
								}else {
									clazType = types[index].getClassName();
								}
								/*//javax.servlet.http.HttpServletRequest javax.servlet.http.HttpServletResponse
								String requestClazName = "javax.servlet.http.HttpServletRequest";
								String responseClazName = "javax.servlet.http.HttpServletResponse";
								if (clazType != null && (clazType.equalsIgnoreCase(requestClazName) || clazType.equalsIgnoreCase(responseClazName))) {
									return;
								}*/
								if (clazType == null) {
									return;
								}
								paramInfo = new ParamInfo();
								paramInfo.setName(name);
								paramInfo.setType(clazType);
							}
							if (paramInfo != null) {
								parameterNames.add(paramInfo);
							}
						}
					};
				}
			}, 0);
		} catch (IOException e) {
		} finally {
			try {
				if (is != null) {
					is.close();
				}
			} catch (Exception e2) {
			}
		}
		return parameterNames;
	}

	private static Set<Class<?>> findClassByPackage(String pkg) {
		Set<Class<?>> classes = new LinkedHashSet<>();
		try {
			if (pkg == null || "".equals(pkg)) {
				return new LinkedHashSet<>();
			}
			String packagePath = pkg.replace(".", File.separator);
			Enumeration<URL> fileUrls = AnnotationConfig.class.getClassLoader().getResources(packagePath);
			while (fileUrls.hasMoreElements()) {
				URL url = fileUrls.nextElement();
				String protocol = url.getProtocol();
				if ("file".equalsIgnoreCase(protocol)) {
					String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
					findClassesByFile(pkg, filePath, classes);
				}
			}
		} catch (IOException e) {
			System.err.println(e.getMessage());
		}
		return classes;
	}

	private static void findClassesByFile(String pkgName, String pkgPath, Set<Class<?>> classes) {
		File dir = new File(pkgPath);
		if (!dir.exists() || !dir.isDirectory()) {
			return;
		}

		// 过滤获取目录，or class文件
		File[] dirfiles = dir.listFiles(new FileFilter() {
			@Override
			public boolean accept(File pathname) {
				return pathname != null && pathname.isDirectory() || pathname.getName().endsWith("class");
			}
		});

		if (dirfiles == null || dirfiles.length == 0) {
			return;
		}

		String className;
		Class clz;
		for (File f : dirfiles) {
			// 文件是目录
			if (f.isDirectory()) {
				findClassesByFile(pkgName + "." + f.getName(), pkgPath + "/" + f.getName(), classes);
				continue;
			}

			// 获取类名，干掉 ".class" 后缀
			className = f.getName();
			className = className.substring(0, className.length() - 6);

			// 加载类
			clz = loadClass(pkgName + "." + className);
			if (clz != null) {
				classes.add(clz);
			}
		}
	}

	private static Class<?> loadClass(String fullClzName) {
		try {
			return Thread.currentThread().getContextClassLoader().loadClass(fullClzName);
		} catch (ClassNotFoundException e) {
			System.err.println(String.format("load class error! clz: {}, e:{}", fullClzName, e.getMessage()));
		}
		return null;
	}

	public static void main(String[] args) throws Exception{
		AnnotationConfig.readAnnotationXml("E:\\IDEA_workspace\\mvc\\src\\main\\resource\\annotation-config.xml");

	}
}
